package se.sll.itintegration.catalogue.gui.ejb;

import java.sql.Timestamp;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import javax.annotation.Resource;
import javax.ejb.SessionContext;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;

import lombok.extern.slf4j.Slf4j;
import se.sll.itintegration.catalogue.gui.entity.CareUnitEntity;
import se.sll.itintegration.catalogue.gui.entity.UserEntity;
import se.sll.service.catalogue.common.AbstractJpaFacade;
import se.sll.service.catalogue.common.exception.BusinessLogicException;
import se.sll.service.catalogue.common.exception.ResourceNotFoundException;
import se.sll.service.catalogue.common.exception.ServiceErrorCode;
import se.sll.service.catalogue.common.exception.ValidationException;

/**
 *
 * @author khaleddaham
 * @author Jiri Uosukainen
 */
@Slf4j
@Stateless
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public class UserEjb extends AbstractJpaFacade<UserEntity> {

    @PersistenceContext(unitName = "guiPU")
    private EntityManager em;

    @Resource
    private SessionContext sessionContext;

    /**
     *
     */
    public UserEjb() {
        super(UserEntity.class);
    }

    @Override
    public void create(UserEntity user) {
        Timestamp timestamp = getTimeStamp();
        user.setCreationTime(timestamp);
        user.setUpdatedTime(timestamp);
        super.create(user);
    }

    @Override
    public void edit(UserEntity user) {
        Timestamp timestamp = getTimeStamp();
        user.setUpdatedTime(timestamp);
        super.edit(user);
    }

    @Override
    protected EntityManager getEntityManager() {
        return em;
    }

    @Override
    protected void setEntityManager(EntityManager em) {
        this.em = em;
    }

    protected void setSessionContext(SessionContext sc) {
        this.sessionContext = sc;
    }

    /**
     *
     * @param username
     * @return
     */
    public List<UserEntity> findByUserName(String username) {
        return findByAttributeValue("hsaID", username);
    }

    public CareUnitEntity updateCareUnit(String unitID) {
        CareUnitEntity cue = this.findCareUnitByUnitID(unitID);
        log.debug("Careunit entity: " + cue);
        if (cue == null) {
            log.debug("Create careunit entity.");
            cue = new CareUnitEntity(unitID);
            em.persist(cue);
            return this.findCareUnitByUnitID(unitID);
        }
        return cue;
    }

    public Set<UserEntity> findForUnit(String unitId) {
        log.debug("UserEjb.findForUnit(): ENTER");
        List<CareUnitEntity> cues = getEntityManager()
                .createQuery("SELECT DISTINCT cue FROM CareUnitEntity cue where cue.hsaID = :unitId", CareUnitEntity.class)
                .setParameter("unitId", unitId).getResultList();
        Set<UserEntity> users = Collections.emptySet();
        if (cues != null && !cues.isEmpty()) {
            CareUnitEntity cue = cues.get(0);
            Set<UserEntity> cueUsers = cue.getUsers();
            if (cueUsers != null) {
                users = cueUsers;
            }
        }
        log.debug("UserEjb.findForUnit(): EXIT");
        return users;
    }

    /**
     * 
     * @param unitId
     * @param hsaIds
     * @throws BusinessLogicException
     * @throws ValidationException
     * @throws ResourceNotFoundException 
     */
    public void setUsersToUnit(String unitId, List<String> hsaIds)
            throws BusinessLogicException, ValidationException, ResourceNotFoundException {
        log.debug("UserEjb.setUsersToUnit(): ENTER");

        if (hsaIds == null) {
            throw new ValidationException(ServiceErrorCode.USER_NAMES_INVALID_EMPTY);
        }

        // The unit
        List<CareUnitEntity> cues = getEntityManager()
                .createQuery("SELECT DISTINCT cue FROM CareUnitEntity cue where cue.hsaID = :unitId", CareUnitEntity.class)
                .setParameter("unitId", unitId).getResultList();
        CareUnitEntity cue = null;
        if (cues.isEmpty()) {
            log.debug("Unit not found, creating unitId: {}", unitId);
            cue = new CareUnitEntity(unitId);
            getEntityManager().persist(cue);
        } else if (cues.size() == 1) {
            cue = cues.get(0);
            log.debug("Found Unit: {}", cue);
        } else if (cues.size() > 1) {
            String msg = String.format("UnitId is not unique, unitId = %s", unitId);
            log.warn(msg);
            throw new ResourceNotFoundException(ServiceErrorCode.UNIT_ID_NOT_UNIQUE, unitId);
        }

        Set<UserEntity> existingUsers = new HashSet<>(findForUnit(unitId));
        Set<String> newHsaIds = new TreeSet<>(hsaIds);

        // First remove existing users that are not on userNames
        Iterator<UserEntity> existingUsersIter = existingUsers.iterator();
        while (existingUsersIter.hasNext()) {
            UserEntity existingUser = existingUsersIter.next();
            String existingHsaID = existingUser.getHsaID();
            if (newHsaIds.contains(existingHsaID)) {
                newHsaIds.remove(existingHsaID);
            } else {
                existingUser.getCareUnits().remove(cue);
                em.merge(existingUser);
                existingUsersIter.remove();
            }
        }
        // Then add the users from userNames that didn't already exist
        for (String newHsaId : newHsaIds) {
            List<UserEntity> e = findByUserName(newHsaId);
            if(e.isEmpty()) {
                throw new ResourceNotFoundException(ServiceErrorCode.USERS_NOT_FOUND, newHsaId);
            }
            UserEntity newUser = e.get(0); 
            newUser.getCareUnits().add(cue);
            em.merge(newUser);
            existingUsers.add(newUser);
        }
        cue.setUsers(existingUsers);
        em.merge(cue);
    }

    /**
     *
     * @param unitIds
     * @param roles
     * @return
     */
    public Set<UserEntity> findUsersByUnitAndRoles(List<String> unitIds, List<UserEntity.ROLES> roles) {
        log.debug("UserEjb.findUsersByUnitAndRoles(): ENTER");
        String qs = "SELECT DISTINCT user FROM UserEntity user, IN (user.careUnits) cu WHERE cu.hsaID IN :unitList AND user.role IN :roles";

        log.debug("Roles included: " + roles);

        TypedQuery<UserEntity> query = em.createQuery(qs, UserEntity.class);
        query.setParameter("unitList", unitIds);
        query.setParameter("roles", roles);

        List<UserEntity> users = query.getResultList();
        Set<UserEntity> userSet = new HashSet<>();

        if (users != null) {
            log.debug("Found users: " + users.size());
            userSet.addAll(users);
        } else {
            log.debug("Found users is null");
        }

        log.debug("UserEjb.findUsersByUnitAndRoles(): EXIT");
        return userSet;
    }

    /**
     *
     * @param hsaID
     * @return
     */
    private CareUnitEntity findCareUnitByUnitID(String hsaID) {
        CareUnitEntity cue;
        try {
            cue = (CareUnitEntity) em.createQuery("select c from CareUnitEntity c where c.hsaID = ?1").setParameter(1, hsaID).getSingleResult();
        } catch (Exception ex) {
            return null;
        }
        return cue;
    }
}
